<?php
namespace App\Common\Lib\Com;

use App\Common as com;
use App\Common\Model\SysRegistry as modelSysRegistry;

/**
 * 系统注册表
 * 版本：0.1
 * 创建日期：2017/02/13
 * 最后修改日期：2017/02/13
 * 更新日志：
 * 	2017/02/13
 * 		1.创建了该文件
 */
class SysRegistry extends com\Lib\LibBase {
	/** 1：键类型：数字 */
	public static $keyTypeOfNumber = 1;
	/** 2：键类型：字符串 */
	public static $keyTypeOfStr = 2;
	/** 3：键类型：json */
	public static $keyTypeOfJson = 3;

	/** {array}：缓存（格式：[ '' ]） */
	protected static $cache = [];

	/**
	 * 读取组
	 * @param string $key_path 值路径
	 * @param array $param 参数数组 <br />
	 * ················· [可空](array)	$param['mustLists']			说明：必须存在的值，不存在时抛出异常。格式：[ {路径}=>{键名,[键名]}, ... ]，注：{路径}不需要包含开头的"$key_path"<br />
	 * ················· [可空](int)	$param['refresh']					说明：[未加入该功能]重新读入，刷新缓存，不管是否已存在缓存中，都重新读取一次。[0][默认]否 [1]是 	<br />
	 * ················· [可空](str)	$param['lock']							说明：是否加锁，默认不加锁，加锁后将强制刷新缓存，仅可在事务中使用。[0][默认]不加锁 [1]加锁	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return array
	 * @throws
	 */
	public static function keyReadGroup( string $key_path, array $param = [] ):array {
		//初始化参数
		$param['mustLists'] = $param['mustLists'] ?? [];
		$param['refresh'] = $param['refresh'] ?? 0;
		$param['lock'] = $param['lock'] ?? 0;
		//
		$key_path = self::format_key_path( $key_path );
		//
		$sys_registry_db = modelSysRegistry::where( [ 'key_path' => [ 'like', "{$key_path}%" ] ] )->field( [ 'key_path', 'key_name', 'key_type', 'key_value' ] );
		if( $param['lock'] == 1 ) $sys_registry_db->lock( true ); //是否上锁
		$registry_data_s = $sys_registry_db->select()->toArray();
		//合并
		$return_data = self::keyValueArrayMerge( $key_path, $registry_data_s );
		//检测必须存在的值
		if( ! empty( $param['mustLists'] ) ){
			if( ! self::checkGroupMustLists( $key_path, $registry_data_s, $param['mustLists'] ) ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError ); //抛出异常
		}
		//
		return $return_data;
	}

	/**
	 * 检测指定列表键是否存在
	 * @param array $registry_data_s 元数据
	 * @param array $mustLists 需检测的数据。格式：[ {路径}=>{键名}, ... ]
	 * @return int [0]检测不通过 [1]检测通过
	 */
	protected static function checkGroupMustLists( string $root_path, array $registry_data_s, array $mustLists ){
		//建立搜索缓存
		$exist_lists = []; //已存在的键缓存，格式：[ "{路径}=>{键名}" => {键值} , ... ]
		foreach ( $registry_data_s as $registry_value ){
			$exist_lists[ "{$registry_value['key_path']}=>{$registry_value['key_name']}" ] = $registry_value['key_value'];
		}
		//检测
		foreach ( $mustLists as $must_path => $must_key ){
			//生成需要检测的键名组
			$must_key_lists = is_array( $must_key ) ? $must_key : [ $must_key ] ;
			//
			foreach ( $must_key_lists as $must_key_value ){
				if( $must_key_value == '' ) continue;
				if( ! isset( $exist_lists[ "{$root_path}/{$must_path}=>{$must_key_value}" ] ) ) return false;
			}
		}
		//
		return true;
	}

	/**
	 * 读注册表（查询数据为空时抛出异常）<br />
	 * @param string $key_path 值路径
	 * @param string $key_name 键名
	 * @param array $param 参数数组 <br />
	 * @return mixed
	 */
	public static function keyReadOrFail( string $key_path, $key_name = NULL, array $param = [] ){
		$param['orFail'] = 1;
		return self::keyRead( $key_path, $key_name, $param );
	}

	/**
	 * 读注册表（目前没有数据缓存功能）<br />
	 * @param string $key_path 值路径
	 * @param string $key_name 键名
	 * @param string $key_value 键值 [NULL]读取目录下的所有数据
	 * @param array $param 参数数组 <br />
	 * ················· [可空](int)	$param['orFail']						说明：查询数据为空时是否需要抛出异常。[0][默认]不抛出 [1]抛出	<br />
	 * ················· [可空](str)	$param['default']						说明：如值不存在，则返回默认值 [默认：NULL]	（注意：[orFail=1]时，默认值配置无效）<br />
	 * ················· [可空](int)	$param['refresh']						说明：[未加入该功能]重新读入，刷新缓存，不管是否已存在缓存中，都重新读取一次。[0][默认]否 [1]是 	<br />
	 * ················· [可空](str)	$param['lock']							说明：是否加数据写锁，默认不加锁，加锁后将强制刷新缓存，仅可在事务中使用。[0][默认]不加锁 [1]加锁	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return mixed 注册表值
	 * 		[$key_name==NULL && 数据不存在]
	 * 			返回 [NULL]
	 * 		[$key_name!=NULL && 数据不存在]
	 * 			返回 [{空数组}]
	 * @throws
	 */
	public static function keyRead( string $key_path, $key_name = null, $default = null, array $param = [] ){
		//初始化参数
		$param['orFail'] = $param['orFail'] ?? 0;
		$param['refresh'] = $param['refresh'] ?? 0;
		$param['lock'] = $param['lock'] ?? 0;
		//
		$key_path = self::format_key_path( $key_path );
		//
		$sys_registry_db = modelSysRegistry::where( [ 'key_path' => $key_path, 'key_name' => $key_name ] )->field( [ 'key_type', 'key_value' ] );
		if( $param['lock'] == 1 ) $sys_registry_db->lock( true ); //是否上锁
		$registry_data = $sys_registry_db->find();
		if( ! empty( $registry_data ) ){
			$return_data =  self::keyValueTypeConvert( $registry_data->key_value, $registry_data->key_type );
		}else{
			$return_data = NULL;
		}
		//@todo : 缓存层
		//缓存多维键的处理（写入时，相关数据要失效）

		//查询数据为空时是否需要抛出异常
		if( $param['orFail'] == 1 && $return_data == NULL ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError ); //抛出异常
		//默认值
		if( $return_data == NULL && $default !== NULL ) $return_data = $default;
		//
		return $return_data;

	}

	/**
	 * 多条数据合并
	 * @param $key_path
	 * @param $key_array
	 * @return array
	 */
	protected static function keyValueArrayMerge( $key_path, $key_array ):array {
		$merge_array = [];
		foreach ( $key_array as $key_value ){
			$index = str_replace( $key_path, '', $key_value['key_path'] );
			$b = &self::getArrayQuote( $merge_array, $index );
			$b[ $key_value['key_name'] ] = self::keyValueTypeConvert( $key_value['key_value'], $key_value['key_type'] );
		}
		return $merge_array;
	}

	/**
	 * 返回数组指定索引的引用地址
	 * @param array $array 数组
	 * @param string $array_index_str 引用的地址（"."号分隔，如引用"$array['a']['b']['c']"则传入"a.b.c"）
	 * @param mixed $init_value 初始化值（如引用的索引不存在，则自动初始化的值）
	 * @return &array 数组索引引用地址
	 */
	protected static function &getArrayQuote( array &$array, $array_index_str, $init_value = NULL ):array {
		if( $array_index_str == '' ) return $array;
		$index_str_array = explode( '/', $array_index_str );
		$index_str = '';
		//生成索引
		foreach ( $index_str_array as $index_str_value ){
			if( $index_str_value == '' ) continue;
			$index_str .= "['{$index_str_value}']";
		}
		//
		$return_quote = null;
		eval("if( ! isset( \$array{$index_str} ) ) \$array{$index_str} = \$init_value;");
		eval("\$return_quote = &\$array{$index_str};");
		return $return_quote;
	}

	/**
	 * 类型转换
	 * @param $key_value
	 * @param $type
	 * @return mixed
	 */
	protected static function keyValueTypeConvert( $key_value, $type ){
		switch ( $type ){
			case self::$keyTypeOfNumber :
				return $key_value;
			case self::$keyTypeOfStr :
				return $key_value;
			case self::$keyTypeOfJson :
				return json_decode( $key_value );
			default :
				return $key_value;
		}
	}

	/**
	 * 注册表值是否存在，存在则返回id
	 * @param string $key_path 值路径
	 * @param string $key_name 键名
	 * @return int [0]不存在 [>0]存在，并返回id
	 */
	public static function hasToID( $key_path, $key_name ){
		return modelSysRegistry::isExistToID( [ 'key_path' => $key_path, 'key_name' => $key_name ] );
	}

	/**
	 * 写入注册表
	 * @param string $key_path 值路径
	 * @param string $key_name 键名
	 * @param string $key_value 键值
	 * @param array $param 参数数组 <br />
	 * ················· [可空](str)	$param['key_type']	说明：键类型（为空时自动判断） [1]数字 [2]字符串 [3]json（$key_value传入数组）(不建议使用)	<br />
	 * ················· [可空](str)	$param['init_type']	说明：初始化类型 [0]动态（不需要初始化，使用时自动创建） [1]静态（需要手动初始化）	<br />
	 * ················· [可空](str)	$param['explain']	说明：创建时需要写入的注释（最长256字符）	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return int [>0]写入成功，并返回数据id
	 */
	public static function keyWrite( $key_path, $key_name, $key_value, $param = [] ):int {
		//检测数据是否存在
		$has_id = self::hasToID( $key_path, $key_name );
		//
		$data = [
			'key_path' => $key_path,
			'key_name' => $key_name,
			'key_value' => $key_value,
			'key_type' => $param['key_type'] ?? NULL,
			'init_type' => $param['init_type'] ?? NULL,
			'explain' => $param['explain'] ?? NULL,
		];
		$save_data = self::extractSaveDataSysRegistry( $has_id ? 1 : 0, $data );
		if( $save_data === NULL ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
		//
		if( $has_id > 0 ){ //更新
			modelSysRegistry::update( $save_data, [ 'id' => $has_id ] );
			$key_data_id = $has_id;

		}else{ //插入
			$res_create_data = modelSysRegistry::create( $save_data );
			$key_data_id = $res_create_data->id;
		}
		//
		return $key_data_id;
	}

	/**
	 * 提取并格式化"sys_registry"表需要写入的数据
	 * @param int $save_type 保存类型 [0]创建 [1]更新
	 * @param array $data 原数据
	 * @return NULL|array [NULL]提取失败 [{数组}]已提取的数据
	 */
	protected static function extractSaveDataSysRegistry( $save_type, $data ){
		$save_data = [];
		//键类型
		if( isset( $data['key_type'] ) ) $save_data['key_type'] = (int)$data['key_type'];
		//键值->是否存在
		if( ! isset( $data['key_value'] ) ) return NULL;
		//键值->自动判断键值类型
		if( ! isset( $save_data['key_type'] ) || $save_data['key_type'] == 0 ){
			if( is_numeric( $data['key_value'] ) ){
				$save_data['key_type'] = self::$keyTypeOfNumber;
			}else if( is_string( $data['key_value'] ) ){
				$save_data['key_type'] = self::$keyTypeOfStr;
			}else if( is_array( $data['key_value'] ) ){
				$save_data['key_type'] = self::$keyTypeOfJson;
			}else{
				$save_data['key_type'] = self::$keyTypeOfStr;
			}
		}
		//键值
		if( $save_data['key_type'] == self::$keyTypeOfJson ){
			$save_data['key_value'] = json_encode( $data['key_value'] );
		}else{
			$save_data['key_value'] = $data['key_value'];
		}
		//初始化类型
		if( isset( $data['init_type'] ) ) $save_data['init_type'] = (int)$data['init_type'];
		//注释
		if( isset( $data['explain'] ) ) $save_data['explain'] = $data['explain'];
		//创建/更新处理
		if( $save_type == 0 ){
			//值路径
			if( isset( $data['key_path'] ) ) $save_data['key_path'] = $data['key_path'];
			//键名
			if( isset( $data['key_name'] ) ) $save_data['key_name'] = $data['key_name'];
		}
		//
		return $save_data;
	}

	/**
	 * 数字自增
	 * @TODO :: 加锁
	 * @param string $key_path
	 * @param string $key_name
	 * @param int $increment_value 自增步进值(不允许为0或负数)。[默认:1]
	 * @param array $param 参数数组 <br />
	 * ················· [可空](float)	$param['default_value']	说明：如果数据不存在，则使用此默认值（不在此进行自增）。[默认:0]	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return int [null]自增失败 [{int}]自增新值
	 */
	public static function increment( $key_path, $key_name, int $increment_value = 1, array $param = [] ){
		// ...
		if( $increment_value <= 0 ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysFunParamError ); //不允许为0或负数
		$param['default_value'] = $param['default_value'] ?? 0;
		//
		$value = self::keyRead( $key_path, $key_name, $param['default_value'] );
		$value += $increment_value;
		// 写入新值
		if( self::keyWrite( $key_path, $key_name, $value ) > 0 ){
			return $value;
		}else{
			return 0;
		}
	}

	/**
	 * 数字自减
	 * @TODO :: 加锁
	 * @param string $key_path
	 * @param string $key_name
	 * @param int $decrement_value 自减步进值(不允许为0或负数)。[默认：1]
	 * @param array $param 参数数组 <br />
	 * ················· [可空](float)	$param['default_value']	说明：如果数据不存在，则使用此默认值（不在此进行自增）。[默认:0]	<br />
	 * ················· [可空/多选_A/必须](unknown_type)	$param['AAAAA']	说明：AAAAA	<br />
	 * @return int [null]自增失败 [{int}]自增新值
	 */
	public static function decrement( $key_path, $key_name, int $decrement_value = 1, array $param = [] ){
		// ...
		if( $decrement_value <= 0 ) throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysFunParamError ); //不允许为0或负数
		$param['default_value'] = $param['default_value'] ?? 0;
		//
		$value = self::keyRead( $key_path, $key_name, $param['default_value'] );
		$value -= $decrement_value;
		// 写入新值
		if( self::keyWrite( $key_path, $key_name, $value ) > 0 ){
			return $value;
		}else{
			return 0;
		}
	}

	/**
	 * @todo : 删除注册表
	 * @param string $key_name 键名
	 * @param string $key_value 键值
	 * @return int [0]删除失败 [1]删除成功
	 */
	public static function keyDel( $key_path, $key_name ){
		throw com\Lib\Err\Exception::makeOfNew( '未做', com\Lib\Err\Code::$sysFunParamError ); // 未做
	}

	/**
	 * 格式化注册表值路径
	 * @param string $key_path
	 * @return string
	 */
	private static function format_key_path( $key_path ):string {
		return rtrim( $key_path, '/ ' );
	}

}
